import { Executor } from '.';
import { IWork } from '..';
import { command, kernel, model, schema } from '../../../base';
import orgCtrl from '../../../controller';
import { IBelong } from '../../target/base/belong';
import { Person } from '../../target/person';
import { Company } from '../../target/team/company';
import { Directory } from '../../thing/directory';

/**
 * 数据申领
 */
export class Acquire extends Executor {
  private target: IBelong | undefined;
  private source: IBelong | undefined;
  private work: IWork | undefined;
  /**
   * 执行器
   */
  async execute(): Promise<boolean> {
    for (const item of this.metadata.acquires) {
      await this.singleExecute(item);
    }
    return true;
  }
  async findWork(): Promise<IWork> {
    if (!this.work) {
      const work = await this.task.findWorkById(this.task.taskdata.defineId);
      if (work) {
        this.work = work;
      }
    }
    if (!this.work) {
      throw new Error('未获取到办事信息！');
    }
    return this.work;
  }
  async singleExecute(item: model.Acquire): Promise<void> {
    this.command.emitter('acquire', 'loading', item.id);
    const onProgress = (p: number, t: number) => {
      if (t == 0) {
        this.command.emitter('acquire', 'progress', { id: item.id, progress: 100 });
        return;
      }
      const progress = Number(((p / t) * 100).toFixed(2));
      this.command.emitter('acquire', 'progress', { id: item.id, progress });
    };
    onProgress(0, 100);
    switch (item.typeName) {
      case '标准':
        await this.standard(item, 'directorys');
        break;
      case '应用':
        await this.standard(item, 'applications');
        break;
      case '资产':
      case '办事':
        switch (item.code) {
          case '_system-things_{period}':
            await this.period(onProgress);
            break;
          default:
            await this.collection(item.code, onProgress, true);
            break;
        }
        break;
      case '财务':
        switch (item.code) {
          case 'financial-period':
            await this.collection(item.code, onProgress);
            await this.initialize();
            break;
          case 'financial-balance':
            await this.collection(item.code, onProgress);
            break;
          case 'depreciation-config':
            await this.depreciationConfig();
            break;
          default:
            await this.collection(item.code, onProgress, false);
            break;
        }
        break;
      case '表单':
        if (item.collName) {
          await this.collection(item.collName, onProgress, true, item.id);
        }
        break;
      case '附件':
        await this.transferFile();
        break;
    }
    onProgress(100, 100);
    this.command.emitter('acquire', 'unloading', item.id);
  }
  /**
   * 加载资源归属
   * @param belongId 归属 ID
   * @returns 归属
   */
  private async loadBelong(belongId: string): Promise<IBelong> {
    const source = await kernel.queryEntityById({ id: belongId });
    if (!source.data) {
      throw new Error('未获取到数据源单位，迁移失败，请联系办事配置人员修改！');
    }
    let belong: IBelong;
    switch (source.data.typeName) {
      case '人员':
        belong = new Person(source.data as schema.XTarget);
        break;
      case '单位':
        belong = new Company(source.data as schema.XTarget, orgCtrl.user);
        break;
      default:
        throw new Error('数据源单位类型非归属，迁移失败，请联系办事配置人员修改！');
    }
    return belong;
  }
  /**
   * 加载数据源归属
   */
  async loadSourceBelong(belongId: string): Promise<IBelong> {
    if (!this.source) {
      this.source = await this.loadBelong(belongId);
    }
    if (!this.source) {
      throw new Error('获取数据源单位失败!');
    }
    return this.source;
  }
  /**
   * 加载目标归属
   */
  loadTargetBelong(): IBelong {
    const belong = [orgCtrl.user, ...orgCtrl.user.companys];
    for (const item of belong) {
      if (item.id == this.task.taskdata.applyId) {
        this.target = item;
        break;
      }
    }
    if (!this.target) {
      throw new Error('获取目标归属组织失败!');
    }
    return this.target;
  }
  /**
   * 迁移标准
   */
  async standard(acquire: model.Acquire, key: 'directorys' | 'applications') {
    const target = this.loadTargetBelong();
    const work = await this.findWork();
    const group = work.directory.target;
    await group.directory.loadContent(true);
    for (const entity of group.directory.standard[key]) {
      if (acquire.id == entity.id) {
        await entity.loadContent(true);
        await entity.copy(target.directory);
      }
    }
  }
  /**
   * 按集合迁移
   */
  async collection(
    collName: string,
    onProgress: (p: number, t: number) => void,
    isBelong: boolean = true,
    formId?: string,
  ) {
    const total = await this.getTotalCount(collName, isBelong, formId);
    if (total > 0) {
      await this.transfer(collName, (p) => onProgress(p, total), isBelong, formId);
    }
  }
  /**
   * 加载账期
   */
  async loadPeriods() {
    const source = await this.loadSourceBelong(this.metadata.belongId);
    const periodColl = source.resource.genColl<schema.XPeriod>('financial-period');
    return await periodColl.loadResult({
      options: { match: { belongId: this.task.taskdata.applyId } },
    });
  }
  /**
   * 快照迁移
   */
  async period(onProgress: (p: number, t: number) => void) {
    const space = this.loadTargetBelong();
    const result = await this.loadPeriods();
    if (result.success && result.data && result.data.length > 0) {
      const data = result.data.sort((a, b) => a.period.localeCompare(b.period));
      if (data.length > 0) {
        await this.transfer(
          `_system-things_${space.financial.getOffsetPeriod(data[0].period, -1)}`,
          undefined,
          true,
        );
        for (let index = 0; index < data.length; index++) {
          const item = data[index];
          await this.transfer(`_system-things_${item.period}`, undefined, true);
          onProgress(index + 1, data.length + 1);
        }
      }
    }
  }
  /**
   * 初始化账期
   */
  async initialize() {
    const target = this.loadTargetBelong();
    const result = await this.loadPeriods();
    if (result.success && result.data && result.data.length > 0) {
      const data = result.data.sort((a, b) => a.period.localeCompare(b.period));
      if (data.length > 0) {
        const first = data[0];
        const init = target.financial.getOffsetPeriod(first.period, -1);
        await target.financial.setInitialize(init);
        await target.financial.setCurrent(data[data.length - 1].period);
      }
    }
  }
  /**
   * 迁移对象
   */
  async depreciationConfig() {
    const target = this.loadTargetBelong();
    const config = await this.loadSourceObject<schema.XConfiguration>(
      '_system-objects',
      'depreciation-config',
    );
    if (config.success && config.data && config.data.length > 0) {
      await target.financial.configuration.setMetadata(config.data[0]);
    }
    const financial = await this.loadSourceObject<schema.XFinancial>(
      '_system-objects',
      'target-financial',
    );
    if (financial.success && financial.data && financial.data.length > 0) {
      let item = financial.data[0];
      if (item.query) {
        const query = await this.loadSourceObject<schema.XQuery>(
          'financial-query',
          item.query,
        );
        if (query.success && query.data && query.data.length > 0) {
          await target.financial.setQuery(query.data[0]);
        }
      }
      if (item.form) {
        const forms = await this.loadSourceObject<schema.XForm>(
          'standard-form',
          item.form,
        );
        if (forms.success && forms.data && forms.data.length > 0) {
          await target.financial.setForm(forms.data[0]);
        }
      }
      if (item.balance) {
        const forms = await this.loadSourceObject<schema.XForm>(
          'standard-form',
          item.balance,
        );
        if (forms.success && forms.data && forms.data.length > 0) {
          await target.financial.setBalance(forms.data[0]);
        }
      }
    }
  }
  /**
   * 加载源对象
   * @param code 代码
   * @returns 对象
   */
  async loadSourceObject<T extends schema.Xbase>(collName: string, id: string) {
    const source = await this.loadSourceBelong(this.metadata.belongId);
    const configColl = source.resource.genColl<T>(collName);
    const options = { options: { match: { id } } };
    return await configColl.loadResult(options);
  }
  /**
   * 获取总进度
   * @param work 办事
   * @returns 总进度
   */
  private async getTotalCount(
    collName: string,
    isBelong: boolean = true,
    formId?: string,
  ): Promise<number> {
    const things = await this.loadData(1, 0, true, collName, isBelong, formId);
    return things.totalCount;
  }
  /**
   * 迁移数据
   * @param work 办事
   * @param onProgress 进度回调
   */
  private async transfer(
    collName: string,
    onProgress?: (p: number) => void,
    isBelong: boolean = true,
    formId?: string,
  ) {
    let loaded = 0;
    let space = this.loadTargetBelong();
    let take = 500;
    let skip = 0;
    let done = false;
    while (!done) {
      const things = await this.loadData(take, skip, false, collName, isBelong, formId);
      await kernel.collectionReplace(space.id, [space.id], collName, things.data);
      skip += things.data.length;
      loaded += things.data.length;
      onProgress?.(loaded);
      if (things.data.length == 0) {
        done = true;
        break;
      }
    }
  }
  /**
   * 迁移文件
   */
  private async transferFile(): Promise<void> {
    const source = await this.loadSourceBelong(this.metadata.belongId);
    const space = this.loadTargetBelong();
    const directories = await source.resource.fileDirectoryColl.loadSpace({
      options: {
        match: {
          code: space.metadata.code,
        },
      },
    });
    if (directories.length > 0) {
      for (const item of directories) {
        const groupDir = new Directory(item, space);
        const children = await space.directory.standard.loadDirectorys();
        let fileDir = children.find((item) => item.name.includes('数据附件'));
        if (!fileDir) {
          const resultDir = await space.directory.create({
            name: '数据附件',
            code: 'dataFile',
            typeName: '目录',
            directoryId: space.id,
          } as schema.XDirectory);
          if (resultDir) {
            fileDir = new Directory(resultDir, space, space.directory);
          }
        }
        if (fileDir) {
          const ready = (await fileDir.loadFiles()).map(
            (item) => item.code.split('/')[1],
          );
          const files = (await groupDir.loadFiles()).filter((item) => {
            return !ready.includes(item.code.split('/')[1]);
          });
          if (files.length > 0) {
            command.emitter('executor', 'taskList', fileDir);
          }
          for (const file of files) {
            const response = await fetch(file.shareInfo().shareLink ?? '');
            if (response.ok) {
              const blob = await response.blob();
              fileDir.createFile(
                file.name,
                new File([blob], file.name, { type: blob.type }),
              );
            }
          }
        }
      }
    }
  }
  /**
   * 加载物
   * @param take 拿几个
   * @param skip 跳过几个
   * @param form 表单
   * @param work 办事
   * @returns 物信息
   */
  private async loadData(
    take: number,
    skip: number,
    requireTotalCount = true,
    collName: string,
    isBelong: boolean = true,
    formId?: string,
  ) {
    let filter: any[] = [];
    let userData: any[] = [];
    if (isBelong) {
      filter = ['belongId', '=', this.task.taskdata.applyId];
    }
    if (formId) {
      userData.push(formId);
    }
    const loadOptions = {
      take: take,
      skip: skip,
      requireTotalCount: requireTotalCount,
      userData: [],
      filter: filter,
    };
    const work = await this.findWork();
    return await kernel.collectionLoad<any[]>(
      this.metadata.belongId,
      [this.task.taskdata.applyId, work.directory.target.id, this.metadata.belongId],
      collName,
      loadOptions,
    );
  }
}
